﻿//-------------------------------------------------------------------
//	Copyright (c) 2010-2014  Zhirnov Andrey
//	This file is part of the "UX Standard Lib" project.
//	See copyright notice in "UX_STL_Ext.h".
//-------------------------------------------------------------------

#ifndef _UXS_GL_MESH_H
#define _UXS_GL_MESH_H

#pragma once

#include "GLStateManager.h"

namespace UX_STL
{
namespace GLShell
{


	//
	// Buffer
	//

	class UX_EXPORT GLBuffer : public GLBaseObject
	{
	protected:
		usize			_uSize;
		GLuint			_uID;
		gl_buffer::type	_eTarget;

	public:
		GLBuffer();
		~GLBuffer();

		bool Create(gl_buffer::type eTarget = gl_buffer::ARRAY);
		void Destroy();

		void Bind(gl_buffer::type eTarget);
		void Bind()		const;
		void Unbind()	const;

		void SetTarget(gl_buffer::type eTarget);
		
		bool Copy(const GLBuffer *pBuffer, usize uReadOffset, usize uWriteOffset, usize uSize = 0);
		bool SetData(const void *pData, usize uSize, gl_usage::type eUsage);
		bool SubData(const void *pData, usize uSize, usize uOffset = 0);
		bool Allocate(usize uSize, gl_usage::type eUsage = gl_usage::DYNAMIC_DRAW);

		bool GetSubData(void *pData, usize uOffset, uint uSize) const;

		// for transform_feedback_buffer, uniform_buffer, atomic_counter_buffer, shader_storage_buffer
		bool BindBase(uint uIndex)	const;
		bool BindRange(uint uIndex, usize uOffset, usize uSize) const;
		bool BindBase(gl_buffer::type eTarget, uint uIndex)	const;
		bool BindRange(gl_buffer::type eTarget, uint uIndex, usize uOffset, usize uSize) const;

		GLuint			Id()		const	{ return _uID; }
		gl_buffer::type	Target()	const	{ return _eTarget; }
		usize			Size()		const	{ return _uSize; }
		bool			IsBinded()	const;
	};



#ifdef UX_GL_USE_VAO

	//
	// Sub Mesh
	//

	class UX_EXPORT GLSubMesh : public GLBaseObject
	{
	protected:
		usize				_uOffset;
		GLuint				_uVAO;
		GLuint				_uIBO;
		usize				_uCount;
		gl_primitive::type	_ePrimitive;
		gl_index::type		_eIndex;		// for DrawElements
		va_info_array_t		_aVAInfo;

	public:
		GLSubMesh();
		GLSubMesh(GLSubMesh &);
		~GLSubMesh();

		bool Create(const uni_array<const GLVertexAttribExt> &aAttribs, GLuint uIBO, usize uCount,
					usize uOffset, gl_primitive::type eMode, gl_index::type eType = gl_index::_UNKNOWN);
		bool Create(const uni_array<const GLVertexAttrib> &aAttribs, GLuint uVBO, GLuint uIBO, usize uCount,
					usize uOffset, gl_primitive::type eMode, gl_index::type eType = gl_index::_UNKNOWN);
		bool GetParams(GLVerticesAttribsExt_t &aAttribs, GLuint &uIBO, usize &uCount,
					usize &uOffset, gl_primitive::type &eMode, gl_index::type &eType) const;
		void Destroy();

		void Bind()   const;
		void Unbind() const;

		void DrawArrays() const;
		void DrawElements() const;
		void DrawArraysInstanced(uint uInstances) const;
		void DrawElementsInstanced(uint uInstances) const;
		void DrawElementsBaseVertex(uint uBaseVertex) const;
		void DrawElementsInstancedBaseVertex(uint uInstances, uint uBaseVertex) const;
		void DrawRangeElements(uint uStart, uint uEnd) const;
		void DrawRangeElementsBaseVertex(uint uStart, uint uEnd, uint uBaseVertex) const;

		GLuint					Id()			const	{ return _uVAO; }
		usize					Count()			const	{ return _uCount; }
		usize					Offset()		const	{ return _uOffset; }
		const void *			OffsetPtr()		const	{ return (const void *)_uOffset; }
		gl_primitive::type		PrimitiveMode()	const	{ return _ePrimitive; }
		gl_index::type			IndexType()		const	{ return _eIndex; }
		va_info_array_t const &	VAInfo()		const	{ return _aVAInfo; }
	};

#else


	//
	// Sub Mesh
	//

	class UX_EXPORT GLSubMesh : public GLBaseObject
	{
	protected:
		usize				_uOffset;
		va_array_t			_aVAO;
		GLuint				_uIBO;
		uint				_uCount;
		gl_primitive::type	_ePrimitive;
		gl_index::type		_eIndex;		// for DrawElements
		va_info_array_t		_aVAInfo;

	public:
		GLSubMesh();
		GLSubMesh(GLSubMesh &);
		~GLSubMesh();
		
		bool Create(const uni_array<const GLVertexAttribExt> &aAttribs, GLuint uIBO, usize uCount,
					usize uOffset, gl_primitive::type eMode, gl_index::type eType = gl_index::_UNKNOWN);
		bool Create(const uni_array<const GLVertexAttrib> &aAttribs, GLuint uVBO, GLuint uIBO, usize uCount,
					usize uOffset, gl_primitive::type eMode, gl_index::type eType = gl_index::_UNKNOWN);
		bool GetParams(GLVerticesAttribsExt_t &aAttribs, GLuint &uIBO, usize &uCount,
					usize &uOffset, gl_primitive::type &eMode, gl_index::type &eType) const;
		void Destroy();

		void Bind()   const;
		void Unbind() const;

		void DrawArrays() const;
		void DrawElements() const;
		void DrawArraysInstanced(uint uInstances) const;
		void DrawElementsInstanced(uint uInstances) const;
		void DrawElementsBaseVertex(uint uBaseVertex) const;
		void DrawElementsInstancedBaseVertex(uint uInstances, uint uBaseVertex) const;
		void DrawRangeElements(uint uStart, uint uEnd) const;
		void DrawRangeElementsBaseVertex(uint uStart, uint uEnd, uint uBaseVertex) const;

		GLuint					Id()		const	{ return 0; }
		va_info_array_t const &	VAInfo()	const	{ return _aVAInfo; }
	};

#endif	// UX_GL_USE_VAO




	//
	// Mesh
	//

	class UX_EXPORT GLMesh : public GLBaseObject
	{
	protected:
		typedef array< GLSubMesh, TSafeCopyStrategy< GLSubMesh > >	submeshes_t;
		
	protected:
		submeshes_t		_aSubMeshes;
		GLBuffer		_cVB,
						_cIB;

	public:
		GLMesh();
		~GLMesh();

		bool Create();
		void Destroy();

		bool CreateSubMesh(const uni_array<const GLVertexAttrib> &aAttribs, uint uCount,
							usize uOffset, gl_primitive::type eMode, gl_index::type eType = gl_index::_UNKNOWN);
		bool SetData(const void *pVertices, usize uVSize, const void *pIndices, usize uISize, gl_usage::type eUsage = gl_usage::STATIC_DRAW);
		bool AddSubMesh(GLSubMesh &sSubMesh);

		void DrawAll();
		void Draw(usize uSubMesh) const;

		GLBuffer const &	VBO()			const	{ return _cVB; }
		GLBuffer const &	IBO()			const	{ return _cIB; }
		GLSubMesh const &	SubMesh(usize i)const	{ return _aSubMeshes[i]; }
		usize				Count()			const	{ return _aSubMeshes.Count(); }
	};




	
//-------------------------- GLBuffer -----------------------------//
/*
=================================================
	устанавливает буфер
=================================================
*/
	inline void GLBuffer::Bind() const
	{
		SMAN()->BindBuffer( _eTarget, _uID );
	}
	
	inline void GLBuffer::Bind(gl_buffer::type eTarget)
	{
		_eTarget = eTarget;
		Bind();
	}
	
/*
=================================================
	устанавливает стандартный буфер
=================================================
*/
	inline void GLBuffer::Unbind() const
	{
		SMAN()->BindBuffer( _eTarget, 0 );
	}

/*
=================================================
	передает буфер в шейдер
=================================================
*/
	inline bool GLBuffer::BindBase(uint index) const
	{
		ASSERT( _eTarget == gl_buffer::ATOMIC_COUNTER or
				_eTarget == gl_buffer::UNIFORM or
				_eTarget == gl_buffer::TRANSFORM_FEEDBACK or
				_eTarget == gl_buffer::SHADER_STORAGE );
		GL_CALL_R( glBindBufferBase( _eTarget, index, _uID ) );
		return true;
	}
	
	inline bool GLBuffer::BindBase(gl_buffer::type eTarget, uint index) const
	{
		ASSERT( eTarget == gl_buffer::ATOMIC_COUNTER or
				eTarget == gl_buffer::UNIFORM or
				eTarget == gl_buffer::TRANSFORM_FEEDBACK or
				eTarget == gl_buffer::SHADER_STORAGE );
		GL_CALL_R( glBindBufferBase( eTarget, index, _uID ) );
		return true;
	}
		
/*
=================================================
	передает часть буфера в шейдер
=================================================
*/
	inline bool GLBuffer::BindRange(uint index, usize offset, usize size) const
	{
		ASSERT( _eTarget == gl_buffer::ATOMIC_COUNTER or
				_eTarget == gl_buffer::UNIFORM or
				_eTarget == gl_buffer::TRANSFORM_FEEDBACK or
				_eTarget == gl_buffer::SHADER_STORAGE );
		GL_CALL_R( glBindBufferRange( _eTarget, index, _uID, offset, size ) );
		return true;
	}
	
	inline bool GLBuffer::BindRange(gl_buffer::type eTarget, uint index, usize offset, usize size) const
	{
		ASSERT( eTarget == gl_buffer::ATOMIC_COUNTER or
				eTarget == gl_buffer::UNIFORM or
				eTarget == gl_buffer::TRANSFORM_FEEDBACK or
				eTarget == gl_buffer::SHADER_STORAGE );
		GL_CALL_R( glBindBufferRange( eTarget, index, _uID, offset, size ) );
		return true;
	}


	
//-------------------------- GLSubMesh ----------------------------//
#ifdef UX_GL_USE_VAO
/*
=================================================
	сделать текущим
=================================================
*/
	inline void GLSubMesh::Bind() const
	{
		SMAN()->BindVertexArray( _uVAO );
	}

	inline void GLSubMesh::Unbind() const
	{
		SMAN()->BindVertexArray( 0 );
	}

#else
/*
=================================================
	сделать текущим
=================================================
*/
	inline void GLSubMesh::Bind() const
	{
		FOR( i, _aVAO )
		{
			if ( _aVAO[i].uLocation == uint8(-1) )
				continue;

			if ( _aVAO[i].uBuffer != 0 )
				GL_CALL( glBindBuffer( gl_buffer::ARRAY, _aVAO[i].uBuffer ) );

			GL_CALL( glEnableVertexAttribArray( _aVAO[i].uLocation ) );
			GL_CALL( glVertexAttribPointer(	_aVAO[i].uLocation,
											_aVAO[i].uCount,
											_aVAO[i].eType,
											_aVAO[i].bNormalize,
											_aVAO[i].uStride,
											(const GLvoid *)_aVAO[i].uOffset ) );
		}
	}

	inline void GLSubMesh::Unbind() const
	{
		FOR( i, _aVAO )
		{
			if ( _aVAO[i].uLocation == uint8(-1) )
				continue;

			GL_CALL( glDisableVertexAttribArray( _aVAO[i].uLocation ) );
		}
	}
	
#endif	// UX_GL_USE_VAO
	

/*
=================================================
	рисовать сабмеш
=================================================
*/
	inline void GLSubMesh::DrawArrays() const
	{
		Bind();
		SMAN()->DrawArrays( _ePrimitive, (GLint)_uOffset, (GLsizei)_uCount );
	}

	inline void GLSubMesh::DrawElements() const
	{
		Bind();
		SMAN()->BindIndexBuffer( _uIBO );
		SMAN()->DrawElements( _ePrimitive, (GLsizei)_uCount, _eIndex, (const void *)_uOffset );
	}

	inline void GLSubMesh::DrawArraysInstanced(uint uInstances) const
	{
		Bind();
		SMAN()->DrawArraysInstanced( _ePrimitive, (GLint)_uOffset, (GLsizei)_uCount, uInstances );
	}

	inline void GLSubMesh::DrawElementsInstanced(uint uInstances) const
	{
		Bind();
		SMAN()->BindIndexBuffer( _uIBO );
		SMAN()->DrawElementsInstanced( _ePrimitive, (GLsizei)_uCount, _eIndex, (const void *)_uOffset, uInstances );
	}

	inline void GLSubMesh::DrawElementsBaseVertex(uint uBaseVertex) const
	{
		Bind();
		SMAN()->BindIndexBuffer( _uIBO );
		SMAN()->DrawElementsBaseVertex( _ePrimitive, (GLsizei)_uCount, _eIndex, (const void *)_uOffset, uBaseVertex );
	}

	inline void GLSubMesh::DrawElementsInstancedBaseVertex(uint uInstances, uint uBaseVertex) const
	{
		Bind();
		SMAN()->BindIndexBuffer( _uIBO );
		SMAN()->DrawElementsInstancedBaseVertex( _ePrimitive, (GLsizei)_uCount, _eIndex, (const void *)_uOffset,
													uInstances, uBaseVertex );
	}

	inline void GLSubMesh::DrawRangeElements(uint uStart, uint uEnd) const
	{
		Bind();
		SMAN()->BindIndexBuffer( _uIBO );
		SMAN()->DrawRangeElements( _ePrimitive, uStart, uEnd, (GLsizei)_uCount, _eIndex, (const void *)_uOffset );
	}

	inline void GLSubMesh::DrawRangeElementsBaseVertex(uint uStart, uint uEnd, uint uBaseVertex) const
	{
		Bind();
		SMAN()->BindIndexBuffer( _uIBO );
		SMAN()->DrawRangeElementsBaseVertex( _ePrimitive, uStart, uEnd, (GLsizei)_uCount, _eIndex,
											(const void *)_uOffset, uBaseVertex );
	}

//-------------------------------------------------------------------

}	// GLShell
}	// UX_STL

#endif	// _UXS_GL_MESH_H